using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using UnityEngine;
using GameFramework;
using Newtonsoft.Json;
using UnityGameFramework.Runtime;
using GFUtility = GameFramework.Utility;

namespace LS.Saving
{
    internal sealed partial class SavingManager: ISavingManager
    {
        private SavingPathType m_SavingPathType = SavingPathType.PersistentData;
        private ISavingHelper m_SavingHelper = null;

        private Dictionary<string, SavingGroup> m_SavingGroups;

        private EventHandler<InternalBeginSaveEventArgs> m_BeginSaveEventHandler;
        private EventHandler<InternalEndSaveEventArgs> m_EndSaveEventHandler;
        private EventHandler<InternalBeginSaveDatabaseEventArgs> m_BeginSaveDatabaseEventHandler;
        private EventHandler<InternalEndSaveDatabaseEventArgs> m_EndSaveDatabaseEventHandler;

        private EventHandler<InternalBeginLoadEventArgs> m_BeginLoadEventHandler;
        private EventHandler<InternalEndLoadEventArgs> m_EndLoadEventHandler;
        private EventHandler<InternalBeginLoadDatabaseEventArgs> m_BeginLoadDatabaseEventHandler;
        private EventHandler<InternalEndLoadDatabaseEventArgs> m_EndLoadDatabaseEventHandler;

        private object m_LockObject = new object();

        public int SavingGroupCount
        {
            get
            {
                return m_SavingGroups.Count;
            }
        }

        public event EventHandler<InternalBeginSaveEventArgs> BeginSave
        {
            add
            {
                m_BeginSaveEventHandler += value;
            }

            remove
            {
                m_BeginSaveEventHandler -= value;
            }
        }

        public event EventHandler<InternalEndSaveEventArgs> EndSave
        {
            add
            {
                m_EndSaveEventHandler += value;
            }

            remove
            {
                m_EndSaveEventHandler -= value;
            }
        }

        public event EventHandler<InternalBeginSaveDatabaseEventArgs> BeginSaveDatabase
        {
            add
            {
                m_BeginSaveDatabaseEventHandler += value;
            }

            remove
            {
                m_BeginSaveDatabaseEventHandler -= value;
            }
        }

        public event EventHandler<InternalEndSaveDatabaseEventArgs> EndSaveDatabase
        {
            add
            {
                m_EndSaveDatabaseEventHandler += value;
            }

            remove
            {
                m_EndSaveDatabaseEventHandler -= value;
            }
        }

        public event EventHandler<InternalBeginLoadEventArgs> BeginLoad
        {
            add
            {
                m_BeginLoadEventHandler += value;
            }

            remove
            {
                m_BeginLoadEventHandler -= value;
            }
        }

        public event EventHandler<InternalEndLoadEventArgs> EndLoad
        {
            add
            {
                m_EndLoadEventHandler += value;
            }

            remove
            {
                m_EndLoadEventHandler -= value;
            }
        }

        public event EventHandler<InternalBeginLoadDatabaseEventArgs> BeginLoadDatabase
        {
            add
            {
                m_BeginLoadDatabaseEventHandler += value;
            }

            remove
            {
                m_BeginLoadDatabaseEventHandler -= value;
            }
        }

        public event EventHandler<InternalEndLoadDatabaseEventArgs> EndLoadDatabase
        {
            add
            {
                m_EndLoadDatabaseEventHandler += value;
            }

            remove
            {
                m_EndLoadDatabaseEventHandler -= value;
            }
        }

        public SavingManager()
        {
            m_SavingPathType = SavingPathType.PersistentData;
            m_SavingGroups = new Dictionary<string, SavingGroup>(StringComparer.Ordinal);
        }

        public void Update(float delta, float unscaledDelta)
        {

        }

        public void SetHelper(ISavingHelper helper)
        {
            if (helper == null)
            {
                throw new LSRuntimeException("Saving helper is invalid.");
            }
            m_SavingHelper = helper;
            foreach (SavingGroup group in m_SavingGroups.Values)
            {
                group.SetupHelper(helper);
            }
        }

        public void SetSavingPathType(SavingPathType pathType)
        {
            if (pathType == SavingPathType.Undefined)
            {
                pathType = SavingPathType.PersistentData;
            }

            m_SavingPathType = pathType;
        }

        public void Shutdown()
        {
            
        }

        public void Load(string groupName)
        {
            var group = GetSavingGroup(groupName);
            if (group == null)
            {
                string err = $"Group {groupName} does not exist.";
                Log.Error(err);
                SendEndLoadEvent(true, err);
                return;
            }

            IDataFile file = group.LoadedFile;
            if (file == null)
            {
                file = group.GetRecentlyFile();
            }

            if (file == null)
            {
                var err = $"File not exist.";
                Log.Error(err);
                SendEndLoadEvent(true, err);
                return;
            }
            
            InternalLoad(group, file, (r, e) =>
            {
                SendEndLoadEvent(false, string.Empty);
            });
        }

        public void Load(string groupName, string fileName)
        {
            var group = GetSavingGroup(groupName);
            if (group == null)
            {
                string err = $"Group {groupName} does not exist.";
                Log.Error(err);
                SendEndLoadEvent(true, err);
                return;
            }

            if (!group.HasFile(fileName))
            {
                var err = $"File {fileName} does not exist.";
                Log.Error(err);
                SendEndLoadEvent(true, err);
                return;
            }

            IDataFile file = group.CreateFileInfo(fileName);
            InternalLoad(group,file, (r, e) =>
            {
                SendEndLoadEvent(false, string.Empty);
            });
        }

        public void Load(string groupName, IDataFile file)
        {
            SendBeginLoadEvent();
            if (file == null)
            {
                var err = new ArgumentNullException("file");
                Log.Error(err.Message);
                SendEndLoadEvent(true, err.Message);
                return;
            }
            var group = GetSavingGroup(groupName);
            if (group == null)
            {
                string err = $"Group {groupName} does not exist.";
                Log.Error(err);
                SendEndLoadEvent(true, err);
                return;
            }
            
            InternalLoad(group,file, (r, e) =>
            {
                SendEndLoadEvent(false, string.Empty);
            });
        }

        public void Save(string groupName, bool createNewFile = false)
        {
            SendBeginSaveEvent();
            var group = GetSavingGroup(groupName);
            if (group == null)
            {
                string err = $"Group {groupName} does not exist.";
                Log.Error(err);
                SendEndSaveEvent(true, err);
                return;
            }

            if (createNewFile)
            {
                string newName = $"{groupName}{DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss-ffff")}";
                IDataFile file = group.CreateFileInfo(newName);
                InternalSave(group, file, (r, e) =>
                {
                    SendEndSaveEvent(false, string.Empty);
                });
            }
            else
            {
                IDataFile file = group.LoadedFile;
                
                if (file == null)
                    file = group.GetRecentlyFile();
                
                if (file == null)
                {
                    var err = $"File not found.";
                    Log.Error(err);
                    SendEndSaveEvent(true, err);
                    return;
                }
                InternalSave(group, file, (r, e) =>
                {
                    SendEndSaveEvent(false, string.Empty);
                });
            }
        }

        public void Save(string groupName, string fileName)
        {
            SendBeginSaveEvent();
            if (string.IsNullOrEmpty(fileName))
            {
                var err = new ArgumentNullException("fileName");
                Log.Error(err.Message);
                SendEndSaveEvent(true, err.Message);
                return;
            }
            var group = GetSavingGroup(groupName);
            if (group == null)
            {
                string err = $"Group {groupName} does not exist.";
                Log.Error(err);
                SendEndSaveEvent(true, err);
                return;
            }
            
            var file = group.CreateFileInfo(fileName);
            InternalSave(group, file, (r, e) =>
            {
                SendEndSaveEvent(false, string.Empty);
            });
        }
        
        public void Save(string groupName, IDataFile file)
        {
            SendBeginSaveEvent();
            if (file == null)
            {
                var err = new ArgumentNullException("file");
                Log.Error(err.Message);
                SendEndSaveEvent(true, err.Message);
                return;
            }
            var group = GetSavingGroup(groupName);
            if (group == null)
            {
                string err = $"Group {groupName} does not exist.";
                Log.Error(err);
                SendEndSaveEvent(true, err);
                return;
            }
            
            InternalSave(group, file, (r, e) =>
            {
                SendEndSaveEvent(false, string.Empty);
            });
        }
        private void InternalLoad(ISavingGroup group, IDataFile file, Action<bool, string> resultAction)
        {
            SendBeginLoadDatabaseEvent(group.Name, file, group.Database);
            group.LoadFile(
                file,
                (result, error) =>
                {
                    SendEndLoadDatabaseEvent(group.Name, file, group.Database, result, error);
                    resultAction(result, error);
                });
        }
        private void InternalSave(ISavingGroup group, IDataFile file, Action<bool, string> resultAction)
        {
            SendBeginSaveDatabaseEvent(group.Name, file, group.Database);
            group.SaveFile(
                file,
                (result, error) =>
                {
                    SendEndSaveDatabaseEvent(group.Name, file, group.Database, result, error);
                    resultAction(result, error);
                });
        }
        public string[] GetAllDataNames(string groupName)
        {
            return GetSavingGroup(groupName)?.Database.GetAllNames()?? Array.Empty<string>();
        }

        public void GetAllDataNames(string groupName, List<string> results)
        {
            GetSavingGroup(groupName)?.Database?.GetAllNames(results);
        }

        public bool IsLoaded(string groupName, string fileName)
        {
            return GetSavingGroup(groupName)?.Loaded(fileName) ?? false;
        }
        
        public bool IsLoaded(string groupName)
        {
            return GetSavingGroup(groupName)?.Loaded() ?? false;
        }
        
        public bool HasFile(string groupName, string  fileName)
        {
            return GetSavingGroup(groupName)?.HasFile(fileName) ?? false;
        }

        public bool HasData(string groupName, string dataName)
        {
            return GetSavingGroup(groupName)?
                .Database?
                .HasData(dataName) ?? false;
        }

        public bool RemoveData(string groupName, string dataName)
        {
            return GetSavingGroup(groupName)?
                .Database?
                .RemoveData(dataName) ?? false;
        }

        public void ClearData(string groupName)
        {
            GetSavingGroup(groupName)
                .Database
                .Clear();
        }

        public void DeleteFile(string groupName)
        {
            GetSavingGroup(groupName)
                .DeleteFile();
        }
        
        public T GetData<T>(string dataName, string groupName)
        {
            var group = GetSavingGroup(groupName);
            
            if (group == null) 
                return default(T);
            
            return group.Database.GetData<T>(dataName);
        }
        
        public object GetObject(string dataName, string groupName)
        {
            return GetSavingGroup(groupName)?
                .Database?
                .GetObject(dataName);
        }
        
        public bool GetBool(string groupName, string dataName)
        {
            return GetSavingGroup(groupName)?
                .Database?
                .GetBool(dataName)?? false;
        }

        public bool GetBool(string groupName, string dataName, bool defaultValue)
        {
            return GetSavingGroup(groupName)?
                .Database?
                .GetBool(dataName) ?? defaultValue;
        }

        public void SetData<T>(string dataName, string groupName, T value)
        {
            GetSavingGroup(groupName)?
                .Database?
                .SetData(dataName, value);
        }
        
        public void SetObject(string dataName, string groupName, object value)
        {
            GetSavingGroup(groupName)?
                .Database?
                .SetObject(dataName, value);
        }
        
        public void SetBool(string groupName, string dataName, bool value)
        {
            GetSavingGroup(groupName)?
                .Database?
                .SetBool(dataName, value);
        }

        public int GetInt(string groupName, string dataName)
        {
            return GetSavingGroup(groupName)?
                .Database?
                .GetInt(dataName)?? 0;
        }

        public int GetInt(string groupName, string dataName, int defaultValue)
        {
            return GetSavingGroup(groupName)?
                .Database?
                .GetInt(dataName)?? defaultValue;
        }

        public void SetInt(string groupName, string dataName, int value)
        {
            GetSavingGroup(groupName)?
                .Database?
                .SetInt(dataName, value);
        }

        public float GetFloat(string groupName, string dataName)
        {
            return GetSavingGroup(groupName)?
                .Database?
                .GetInt(dataName)?? 0;
        }

        public float GetFloat(string groupName, string dataName, float defaultValue)
        {
            return GetSavingGroup(groupName)?
                .Database?
                .GetFloat(dataName)?? defaultValue;
        }

        public void SetFloat(string groupName, string dataName, float value)
        {
            GetSavingGroup(groupName)?
                .Database?
                .SetFloat(dataName, value);
        }

        public string GetString(string groupName, string dataName)
        {
            return GetSavingGroup(groupName)?
                .Database?
                .GetString(dataName)?? string.Empty;
        }

        public string GetString(string groupName, string dataName, string defaultValue)
        {
            return GetSavingGroup(groupName)?.Database?.GetString(dataName)?? defaultValue;
        }

        public void SetString(string groupName, string dataName, string value)
        {
            GetSavingGroup(groupName)?.Database?.SetString(dataName, value);
        }

        public int GetGroupCount()
        {
            return m_SavingGroups.Count;
        }

        public int GetDataCount(string groupName)
        {
            return GetSavingGroup(groupName)?.Database?.Count ?? 0;
        }

        public bool HasSavingGroup(string groupName)
        {
            return m_SavingGroups.ContainsKey(groupName);
        }

        public bool AddSavingGroup(string groupName)
        {
            return AddSavingGroup(groupName, groupName);
        }

        public bool AddSavingGroup(string groupName, string folderName)
        {
            return AddSavingGroup(groupName, folderName, null);
        }

        public bool AddSavingGroup(string groupName, string folderName, string fileExtension)
        {
            if (string.IsNullOrEmpty(groupName))
            {
                throw new Exception("Saving group name is invalid.");
            }

            if (HasSavingGroup(groupName))
            {
                return false;
            }
            
            string rootPath = SavingUtil.GetRootPath(m_SavingPathType);
            string folderPath = GFUtility.Path.GetRegularPath(Path.Combine(rootPath, folderName));
            SavingManager.SavingGroup savingGroup = new SavingGroup(groupName, folderPath, fileExtension);
            savingGroup.SetupHelper(m_SavingHelper);
            m_SavingGroups.Add(groupName, savingGroup);

            return true;
        }

        public ISavingGroup GetSavingGroup(string groupName)
        {
            if (m_SavingGroups.TryGetValue(groupName, out var group))
            {
                return group;
            }
            return null;
        }

        public ISavingGroup[] GetAllSavingGroup()
        {
            return m_SavingGroups.Values.OfType<ISavingGroup>().ToArray();
        }

        public void GetAllSavingGroup(List<ISavingGroup> results)
        {
            results.Clear();
            foreach (var item in m_SavingGroups)
            {
                results.Add(item.Value);
            }
        }

        public Database GetDatabase(string groupName)
        {
            var group = GetSavingGroup(groupName);

            if (group == null)
                return null;

            return group.Database;
        }

        public bool HasDataFile(string groupName, string fileName)
        {
            var group = GetSavingGroup(groupName);

            if (group == null)
                return false;

            return group.HasFile(fileName);
        }

        private void SendBeginSaveEvent()
        {
            var args = GameFramework.ReferencePool.Acquire<InternalBeginSaveEventArgs>();
            m_BeginSaveEventHandler?.Invoke(this, args);
            ReferencePool.Release(args);
        }

        private void SendEndSaveEvent(bool errorBreak, string errorMessage)
        {
            var args = GameFramework.ReferencePool.Acquire<InternalEndSaveEventArgs>();
            args.ProcessBreak = errorBreak;
            args.Error = errorMessage;
            m_EndSaveEventHandler?.Invoke(this, args);
            ReferencePool.Release(args);
        }

        private void SendBeginSaveDatabaseEvent(string groupName, IDataFile file, Database database)
        {
            var args = GameFramework.ReferencePool.Acquire<InternalBeginSaveDatabaseEventArgs>();
            args.Database = database;
            args.File = file;
            args.GroupName = groupName;
            m_BeginSaveDatabaseEventHandler?.Invoke(this, args);
            ReferencePool.Release(args);
        }

        private void SendEndSaveDatabaseEvent(string groupName, IDataFile file, Database database, bool success, string errorMessage)
        {
            var args = GameFramework.ReferencePool.Acquire<InternalEndSaveDatabaseEventArgs>();
            args.Database = database;
            args.File = file;
            args.GroupName = groupName;
            args.Success = success;
            args.ErrorMessage = errorMessage;

            m_EndSaveDatabaseEventHandler?.Invoke(this, args);
            ReferencePool.Release(args);
        }

        private void SendBeginLoadEvent()
        {
            var args = GameFramework.ReferencePool.Acquire<InternalBeginLoadEventArgs>();
            m_BeginLoadEventHandler?.Invoke(this, args);
            ReferencePool.Release(args);
        }

        private void SendEndLoadEvent(bool errorBreak, string errorMessage)
        {
            var args = GameFramework.ReferencePool.Acquire<InternalEndLoadEventArgs>();
            args.ProcessBreak = errorBreak;
            args.Error = errorMessage;
            m_EndLoadEventHandler?.Invoke(this, args);
            ReferencePool.Release(args);
        }

        private void SendBeginLoadDatabaseEvent(string groupName, IDataFile file, Database database)
        {
            var args = GameFramework.ReferencePool.Acquire<InternalBeginLoadDatabaseEventArgs>();
            args.Database = database;
            args.File = file;
            args.GroupName = groupName;
            m_BeginLoadDatabaseEventHandler?.Invoke(this, args);
            ReferencePool.Release(args);
        }

        private void SendEndLoadDatabaseEvent(string groupName, IDataFile file, Database database, bool success, string errorMessage)
        {
            var args = GameFramework.ReferencePool.Acquire<InternalEndLoadDatabaseEventArgs>();
            args.Database = database;
            args.File = file;
            args.GroupName = groupName;
            args.Success = success;
            args.ErrorMessage = errorMessage;

            m_EndLoadDatabaseEventHandler?.Invoke(this, args);
            ReferencePool.Release(args);
        }

    }
}
